from stacking.loader.processing.sim.simulation import SimObserver

        
class InitialStocks(SimObserver):
    def init(self, warehouse):
        SimObserver.init(self, warehouse)
        self.initial_stock = dict()  # {id(stack or crane): [item_id]}
        self.not_yet_moved = set(self.items.iterkeys())
        
    def pre_execute(self, crane, action):
        first_move_items = [i for i in action.items if i in self.not_yet_moved]
        if len(first_move_items) == 0:
            return
        assert first_move_items == action.items[-len(first_move_items):]
        self.not_yet_moved.difference_update(action.items)
        
        if action.is_pickup and action.internal:
            loc_id = action.place
        elif action.is_putdown:
            loc_id = crane.id
        else:
            # pickup from a boundary place (entrance or exit). Do some checks 
            # and leave since boundary places do not have initial stock.
            assert first_move_items == action.items
            assert all(self.warehouse.get_location(self.items[item_id]) is None
                       for item_id in action.items)
            return
        
        if loc_id not in self.initial_stock:
            self.initial_stock[loc_id] = list(first_move_items)
        else:
            self.initial_stock[loc_id].extend(first_move_items)
            
        # now we put the items at the top/bottom of the stack/crane 
        # to that the simulation can proceed without errors
        loc = self.warehouse.objects[loc_id]
        for item_id in first_move_items:
            loc.insert(0, self.items[item_id])
            
        # validate the location of all items
        for item_id in action.items:
            assert self.warehouse.get_location(self.items[item_id]) is loc
        # and that the items are at the top/bottom of the stack/crane in the correct order
        assert (list(reversed(loc.items[-len(action.items):])) == 
                [self.items[item_id] for item_id in action.items])
        
    def end(self):
        """Here we simply reverse the lists of items because they were accumulated in reverse 
        order. E.g. the last item to appear as initial stock at a stack is obviously under all 
        the others, therefore it should be the first element in the item list (note that when new 
        items are discovered, they are appended to the list, and that's why the lists must be 
        reversed now)."""
        assert len(self.not_yet_moved) == 0
        for item_ids in self.initial_stock.itervalues():
            item_ids.reverse()
        self.validate()
        return self.initial_stock
        
    def validate(self):
        # first we create a list of initial locations for each item
        initial_loc = dict()
        for loc, item_ids in self.initial_stock.iteritems():
            for item_id in item_ids:
                try:
                    initial_loc[item_id].append(loc)
                except KeyError:
                    initial_loc[item_id] = [loc]
        # and then check if each list has more than one location
        errors = 0
        for item_id, locs in initial_loc.iteritems():
            if len(locs) > 1:
                print "ERROR: multiple initial locations for %s - %s" % (item_id, locs)
                errors += 1
        if errors > 0:
            raise Exception("%d errors detected with initial stock" % errors)
            
            
def apply_initial_stock(warehouse, items, initial_stock):
    """Sets an empty warehouse's state to the given initial stock dictionary."""
    assert all(len(crane.items) == 0 for crane in warehouse.cranes)
    assert all(len(stack.items) == 0 for stack in warehouse.stacks)
    item_map = dict((item.id, item) for item in items)
    for loc_id, item_ids in initial_stock.iteritems():
        loc = warehouse.objects[loc_id]
        for item_id in item_ids:
            loc.append(item_map[item_id])
            
            
